package com.gitee.dengmin.tenant.interceptor;

import com.gitee.dengmin.tenant.holder.TenantHolder;
import com.gitee.dengmin.tenant.models.Tenant;
import com.gitee.dengmin.tenant.parser.SQLParser;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.update.Update;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

@Slf4j
@Intercepts({
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class TenantInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        MappedStatement ms = (MappedStatement)invocation.getArgs()[0];
        Object parameterObject = null;
        if(invocation.getArgs().length > 1){
            parameterObject = invocation.getArgs()[1];
        }
        BoundSql boundSql = ms.getBoundSql(parameterObject);

        BoundSql newBoundSql = new BoundSql(
                ms.getConfiguration(),
                replace(boundSql.getSql()), //sql替换
                boundSql.getParameterMappings(),
                boundSql.getParameterObject()
        );

        MappedStatement.Builder build = new MappedStatement.Builder(
                ms.getConfiguration(),
                ms.getId(),
                new CustomSqlSource(newBoundSql),
                ms.getSqlCommandType()
        );
        build.resource(ms.getResource());
        build.fetchSize(ms.getFetchSize());
        build.statementType(ms.getStatementType());
        build.keyGenerator(ms.getKeyGenerator());
        build.timeout(ms.getTimeout());
        build.parameterMap(ms.getParameterMap());
        build.resultMaps(ms.getResultMaps());
        build.cache(ms.getCache());

        MappedStatement newStmt = build.build();
        //替换原来的MappedStatement
        invocation.getArgs()[0] = newStmt;

        return invocation.proceed();
    }

    private String replace(String sql) throws JSQLParserException {
        Statement stmt = CCJSqlParserUtil.parse(sql);
        Tenant tenant = TenantHolder.get();
        if(null == tenant){
            return sql;
        }
        String schemeName = String.format("`%s`", tenant.getDbName());
        if(stmt instanceof Insert){
            Insert insert = (Insert)stmt;
            return SQLParser.doInsert(insert, schemeName);
        }else if(stmt instanceof Update){
            Update update = (Update) stmt;
            return SQLParser.doUpdate(update, schemeName);
        }else if(stmt instanceof Delete){
            Delete delete = (Delete) stmt;
            return SQLParser.doDelete(delete, schemeName);
        }else if(stmt instanceof Select){
            Select select = (Select)stmt;
            return SQLParser.doSelect(select, schemeName);
        }
        throw new RuntimeException("非法SQL语句 不可能执行到这里");
    }

    public static class CustomSqlSource implements SqlSource{

        private BoundSql boundSql;

        protected CustomSqlSource(BoundSql boundSql){
            this.boundSql = boundSql;
        }

        @Override
        public BoundSql getBoundSql(Object o) {
            return boundSql;
        }
    }

}
